Semaphores

Semaphores are like tokens to access a critical region, where while it's being used, other processes/threads cannot access the critical region

Semaphore Notation

The semaphore mechanism provides two operations

A thread must call wait() before it enters a critical region

The Producer-Consumer Problem

This problem is a good way to illustrate how semaphores work

Buffer

The problem and solution can be modelled as two threads trying to access a buffer

Consider:

class Buffer {
	private int store;
	public void insert(int item) {
		store = item;
	}
	public int remove() {
		return store;
	}
}

Producer and Consumer

Consider:

class Producer extends Thread {
	private Buffer buff;
	public Producer(Buffer b) {
		buff = b;
	}
	public void run() {
		int m;
		...
		buff.insert(m);
		...
	}
}

The consumer object looks the same but it removes from the buffer instead
n = buff.remove();

None of this has protection for constraints mentioned

Semaphore

class Buffer  {
	private int store;
	private volatile boolean empty = true;
	public void insert(int item) {
		while(!empty); // simulating wait()
		store = item;
		empty = false;  // simulating signal()
	}
	public int remove() {
		while(empty);   // simulating wait()
		empty = true;  // simulating signal()
		return store;
	}
}

The volatile keyword prevents variables from being cached (always uses latest value)
This version prevents threads trying to insert if buffer is full (or remove if buffer is empty)

Spinlock

Before inserting anything, the producer waits for the buffer to be empty

This concept is known as spinlock or busy waiting

A blocked thread gets time on the CPU to effectively do nothing (go around an empty while loop)

Yielding

It could be more efficient for the waiting thread to give up control of the CPU

public void insert(int item) {
	while(!empty) {
		Thread.yield();
	}
	store = item;
	empty = false;
}

The yield() method tells JVM that the thread is willing to block

Here, you would also use the synchronized keyword

public synchronized void insert(int item)

This ensures that only one thread can be inside this subroutine at a time, so no two producers are inserting to the buffer at the same time
If a thread tries to call a synchronized method, but another thread is already inside that method, the new thread calling the method will have to wait

Java Object Locks

Every Java object has a lock associated with it

Wait and Notify

Java provides methods to implement semaphores

public synchronized void insert(int item) {
	while(!empty) {
		try {
			wait();
		} catch(InterruptedException e) {}
	}
	store = item;
	empty = false;
	notify();
}

These methods will always work regardless of the JVM or platform, unlike yield which could be ignored by JVM

Entry and Wait Sets

The wait() call suspends current thread and moves it to the wait set
The notify() call moves an arbitrary thread from the wait set to the entry set (The choice of thread depends on the JVM implementation)
![[Pasted image 20250519074040.png]]
Can also call notifyAll() to allow all threads in the wait set to compete for the chance to resume

Java Semaphore Class

Java provides a Semaphore class that can be used

import java.util.concurrent.*;

Specify how many permits the semaphore allows (counting semaphore)

Semaphore sem = new Semaphore(1);

When the acquire() method is called:

class Buferr {
	private int store;
	private Semaphore sem = new Semaphore(1);
	public synchronized void insert(int item) {
		try { sem.acquire(); } catch(InterruptedException e) {}
		store = item;
		sem.release();
	}
	public synchronized int remove() {
		try { sem.acquire(); } catch(InterruptedException e) {}
		int item = store;
		sem.release();
		return item;
	}
}

This is the proper way to implement semaphores in Java